package org.eclipse.epsilon.emc.jdt2;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;

public class ReflectiveASTVisitor extends ASTVisitor implements ASTReflection {
	protected List<Object> all = null;
	protected String type = null;
	protected boolean ofTypeOnly = false;
	protected Collection<IJavaProject> projects = null;
	protected boolean ifBindings = false;

	protected String keyWord = null;
	protected boolean isConstructor = false;

	protected boolean isOne = false;
	protected ASTNode result = null;

	public ReflectiveASTVisitor(Collection<IJavaProject> projects,
			boolean ifBindings) throws JavaModelException {
		this.projects = projects;
		this.ifBindings = ifBindings;
	}

	private Collection<?> getAll(String type) throws JavaModelException {
		all = new ArrayList<Object>();
		this.type = type;
		// System.out.println("AST model: " + type);
		for (IJavaProject project : projects) {
			for (IPackageFragment packageFragment : project
					.getPackageFragments()) {
				if (packageFragment.getKind() == IPackageFragmentRoot.K_SOURCE) {
					for (ICompilationUnit compilationUnit : packageFragment
							.getCompilationUnits()) {
						ASTParser parser = ASTParser.newParser(AST.JLS4);
						parser.setKind(ASTParser.K_COMPILATION_UNIT);
						parser.setSource(compilationUnit);
						// a switch for turning on/off resolve bindings
						parser.setResolveBindings(ifBindings);
						parser.createAST(null).accept(this);
					}
				}
			}
		}
		return all;
	}

	public Collection<?> getAllOfKind(String type) throws JavaModelException {
		keyWord = null;
		this.ofTypeOnly = false;
		return getAll(type);
	}

	public Collection<?> getAllOfType(String type) throws JavaModelException {
		keyWord = null;
		this.ofTypeOnly = true;
		return getAll(type);
	}

	@Override
	public void preVisit(ASTNode node) {
		if (node.getClass().getSimpleName().equals(type)) {
			if (keyWord != null) {
				if (node instanceof TypeDeclaration) {
					TypeDeclaration td = (TypeDeclaration) node;
					System.out.println(td.getName());
					if (td.getName().toString().equals(keyWord)) {
						if (isOne)
							result = node;
						else
							all.add(node);
					}
				} else if (node instanceof MethodDeclaration) {
					MethodDeclaration md = (MethodDeclaration) node;
					if (isConstructor) {
						if (md.isConstructor()) {
							if (isOne)
								result = node;
							else
								all.add(node);
						}
					} else if (md.getName().toString().equals(keyWord)) {
						if (isOne)
							result = node;
						else
							all.add(node);
					}
				} else if (node instanceof FieldDeclaration) {
					FieldDeclaration fd = (FieldDeclaration) node;
					@SuppressWarnings("unchecked")
					List<Object> tmpL = (List<Object>) fd
							.getStructuralProperty(FieldDeclaration.FRAGMENTS_PROPERTY);
					for (Object o : tmpL) {
						VariableDeclarationFragment s = (VariableDeclarationFragment) o;
						if (s.getName().toString().equals(keyWord)) {
							if (isOne)
								result = node;
							else
								all.add(node);
						}
					}
				}
			} else {
				all.add(node);
			}
		} else if (!ofTypeOnly) {
			Class<?> superClass = node.getClass().getSuperclass();
			while (superClass != Object.class) {
				if (superClass.getSimpleName().equals(type)) {
					if (keyWord != null) {
						if (node instanceof TypeDeclaration) {
							TypeDeclaration td = (TypeDeclaration) node;
							if (td.getName().toString().equals(keyWord)) {
								if (isOne)
									result = node;
								else
									all.add(node);
								break;
							}
						} else if (node instanceof MethodDeclaration) {
							MethodDeclaration md = (MethodDeclaration) node;
							if (isConstructor) {
								if (md.isConstructor()) {
									if (isOne)
										result = node;
									else
										all.add(node);
								}
							} else if (md.getName().toString().equals(keyWord)) {
								if (isOne)
									result = node;
								else
									all.add(node);
							}
						} else if (node instanceof FieldDeclaration) {
							System.out.println("!!!!");
							FieldDeclaration fd = (FieldDeclaration) node;
							System.out.println(fd.getProperty("type"));
						}
					} else {
						all.add(node);
						break;
					}
				}
				superClass = superClass.getSuperclass();
			}
		}
	}

	@Override
	public Collection<?> select(Collection<ICompilationUnit> compilationUnits,
			String type, String keyWord) throws JavaModelException {
		// TODO Auto-generated method stub

		System.out.println("ast model select: " + compilationUnits.size());

		// initialise variable
		all = new ArrayList<Object>();

		if (type.equals(JdtModel.CONSTRUCTOR_DEC)) {
			this.type = JdtModel.METHOD_DEC;
			isConstructor = true;
		} else {
			this.type = type;
			isConstructor = false;
		}

		for (ICompilationUnit compilationUnit : compilationUnits) {
			this.keyWord = keyWord;
			ASTParser parser = ASTParser.newParser(AST.JLS4);
			parser.setKind(ASTParser.K_COMPILATION_UNIT);
			parser.setSource(compilationUnit);
			// a switch for turning on/off resolve bindings
			parser.setResolveBindings(ifBindings);
			parser.createAST(null).accept(this);

		}
		return all;
	}

	@Override
	public Object selectOne(ICompilationUnit compilationUnit, String type,
			String keyWord) {
		// TODO Auto-generated method stub
		System.out.println("ast model select: "
				+ compilationUnit.getElementName());
		isOne = true;
		// initialise variable
//		all = new ArrayList<Object>();

		if (type.equals(JdtModel.CONSTRUCTOR_DEC)) {
			this.type = JdtModel.METHOD_DEC;
			isConstructor = true;
		} else {
			this.type = type;
			isConstructor = false;
		}

		// for (ICompilationUnit compilationUnit : compilationUnits) {
		this.keyWord = keyWord;
		ASTParser parser = ASTParser.newParser(AST.JLS4);
		parser.setKind(ASTParser.K_COMPILATION_UNIT);
		parser.setSource(compilationUnit);
		// a switch for turning on/off resolve bindings
		parser.setResolveBindings(ifBindings);
		parser.createAST(null).accept(this);

		// }
		return result;
	}

}
